document.addEventListener('DOMContentLoaded', () => {
  const sidebar = document.getElementById('sidebar'),
    resizer = document.getElementById('resizer'),
    sidebarActions = document.getElementById('sidebar-actions'),
    openButtonsContainer = document.getElementById('open-buttons-container'),
    openFolderBtn = document.getElementById('openFolderBtn'),
    openFileBtn = document.getElementById('openFileBtn'),
    openNewFolderBtn = document.getElementById('openNewFolderBtn'),
    fileTreeDiv = document.getElementById('file-tree'),
    treePlaceholder = document.getElementById('tree-placeholder'),
    editorWrapper = document.getElementById('editor-wrapper'),
    editorEl = document.getElementById('editor'),
    lineNumbersEl = document.getElementById('line-numbers'),
    editorPlaceholder = document.getElementById('editor-placeholder'),
    saveFileBtn = document.getElementById('saveFileBtn'),
    filePathDisplay = document.getElementById('file-path-display'),
    contextMenu = document.getElementById('context-menu')
  let rootHandle,
    activeFile = {},
    handleMap = {},
    contextTarget = {},
    draggedItem = null
  const icons = {chevronRight: '▸', chevronDown: '▾', newFile: '＋', newFolder: '⊕'}

  const db = {
    _db: null,
    open: () =>
      new Promise((res, rej) => {
        const r = indexedDB.open('file-editor-session', 1)
        r.onupgradeneeded = () => r.result.createObjectStore('session')
        r.onsuccess = () => {
          db._db = r.result
          res()
        }
        r.onerror = e => rej(e.target.error)
      }),
    get: async key => {
      if (!db._db) await db.open()
      return new Promise((res, rej) => {
        const tx = db._db.transaction('session', 'readonly'),
          r = tx.objectStore('session').get(key)
        tx.oncomplete = () => res(r.result)
        tx.onerror = e => rej(e.target.error)
      })
    },
    set: async (key, val) => {
      if (!db._db) await db.open()
      return new Promise((res, rej) => {
        const tx = db._db.transaction('session', 'readwrite')
        tx.objectStore('session').put(val, key)
        tx.oncomplete = () => res()
        tx.onerror = e => rej(e.target.error)
      })
    },
    clear: async () => {
      if (!db._db) await db.open()
      return new Promise((res, rej) => {
        const tx = db._db.transaction('session', 'readwrite')
        tx.objectStore('session').clear()
        tx.oncomplete = () => res()
        tx.onerror = e => rej(e.target.error)
      })
    },
  }

  const verifyPermission = async handle => {
    if (!handle) return false
    const opts = {mode: 'readwrite'}
    try {
      if ((await handle.queryPermission(opts)) === 'granted') return true
      return (await handle.requestPermission(opts)) === 'granted'
    } catch (err) {
      console.error('权限验证失败:', err)
      return false
    }
  }

  const restoreSession = async () => {
    try {
      const savedHandle = await db.get('rootHandle')
      if (await verifyPermission(savedHandle)) {
        await resetAndInitialize(
          savedHandle,
          (await db.get('expandedFolders')) || [],
          await db.get('openFilePath')
        )
      } else {
        showInitialUI()
      }
    } catch (e) {
      showInitialUI()
    }
  }

  const showInitialUI = () => {
    openButtonsContainer.style.display = 'block'
    sidebarActions.style.display = 'none'
    fileTreeDiv.innerHTML = ''
    treePlaceholder.textContent = '将文件夹或文件拖拽至此打开'
    treePlaceholder.style.display = 'block'
  }

  window.addEventListener('beforeunload', () => {
    if (rootHandle) {
      db.set('rootHandle', rootHandle)
      db.set(
        'expandedFolders',
        [...document.querySelectorAll('.tree-item.open')].map(el => el.dataset.path)
      )
      db.set('openFilePath', activeFile.path || '')
    }
  })

  const resetAndInitialize = async (handle, expanded = [], openPath = null) => {
    rootHandle = handle
    activeFile = {}
    fileTreeDiv.innerHTML = ''
    handleMap = {}
    await db.clear()
    await db.set('rootHandle', handle)
    await initializeUIForFolder(expanded, openPath)
  }

  const initializeUIForFolder = async (expanded = [], openPath = null) => {
    await renderTree(rootHandle, fileTreeDiv, '', expanded, openPath)
    openButtonsContainer.style.display = 'none'
    sidebarActions.style.display = 'flex'
    treePlaceholder.style.display = 'none'
  }

  const initializeUIForSingleFile = async fileHandle => {
    rootHandle = null
    activeFile = {}
    handleMap = {}
    await db.clear()
    showInitialUI()
    treePlaceholder.textContent = `正在编辑:${fileHandle.name}`
    await openFile(fileHandle)
  }

  const promptAndOpenFolder = async () => {
    try {
      const handle = await window.showDirectoryPicker()
      if (handle && (await verifyPermission(handle))) await resetAndInitialize(handle)
    } catch (err) {}
  }

  const promptAndOpenFile = async () => {
    try {
      const [handle] = await window.showOpenFilePicker()
      if (handle && (await verifyPermission(handle))) await initializeUIForSingleFile(handle)
    } catch (err) {}
  }

  openFolderBtn.addEventListener('click', promptAndOpenFolder)
  openFileBtn.addEventListener('click', promptAndOpenFile)
  openNewFolderBtn.addEventListener('click', promptAndOpenFolder)

  sidebarActions.addEventListener('click', e => {
    const action = e.target.dataset.action
    if (!rootHandle) return
    if (action === 'new-root-file') {
      promptForNewItem(rootHandle, fileTreeDiv, 'file', '')
    } else if (action === 'new-root-folder') {
      promptForNewItem(rootHandle, fileTreeDiv, 'directory', '')
    }
  })

  const initResizer = () => {
    const move = e =>
      (sidebar.style.width = `${Math.max(200, Math.min(e.clientX, window.innerWidth * 0.5))}px`)
    const up = () => document.removeEventListener('mousemove', move)
    resizer.addEventListener('mousedown', e => {
      e.preventDefault()
      document.addEventListener('mousemove', move)
      document.addEventListener('mouseup', up, {once: true})
    })
  }

  const initEditor = () => {
    editorEl.addEventListener('input', () => {
      if (activeFile.handle) saveFileBtn.disabled = false
      updateLineNumbers()
    })
    editorEl.addEventListener('scroll', () => {
      lineNumbersEl.scrollTop = editorEl.scrollTop
    })
    editorEl.addEventListener('keydown', function (e) {
      if (e.key === 'Tab') {
        e.preventDefault()
        const start = this.selectionStart
        this.value = this.value.substring(0, start) + '  ' + this.value.substring(this.selectionEnd)
        this.selectionStart = this.selectionEnd = start + 2
      }
    })
  }

  const updateLineNumbers = () => {
    lineNumbersEl.innerHTML = Array.from(
      {length: editorEl.value.split('\n').length || 1},
      (_, i) => i + 1
    ).join('<br>')
  }

  const renderTree = async (dirHandle, pEl, pPath, expanded = [], openPath = null) => {
    const entries = []
    for await (const entry of dirHandle.values()) entries.push(entry)
    entries.sort((a, b) =>
      a.kind !== b.kind
        ? a.kind === 'directory'
          ? -1
          : 1
        : a.name.localeCompare(b.name, undefined, {numeric: true})
    )
    pEl.innerHTML = ''
    for (const entry of entries) {
      const newPath = pPath ? `${pPath}/${entry.name}` : entry.name
      const item = createTreeItem(entry, pEl, newPath)
      if (entry.kind === 'directory' && expanded.includes(newPath))
        await toggleFolder(item, true, expanded, openPath)
      if (entry.kind === 'file' && newPath === openPath) await openFile(entry, item)
    }
  }

  const createTreeItem = (entry, pEl, path) => {
    const item = document.createElement('div'),
      itemContent = document.createElement('div'),
      icon = document.createElement('span'),
      name = document.createElement('span')
    item.className = `tree-item ${entry.kind}`
    item.dataset.name = entry.name
    item.dataset.path = path
    handleMap[path] = entry
    itemContent.className = 'tree-item-content'
    itemContent.draggable = true
    icon.className = 'tree-item-icon'
    name.className = 'tree-item-name'
    name.textContent = entry.name
    name.title = entry.name
    itemContent.append(icon, name)
    item.appendChild(itemContent)
    if (entry.kind === 'directory') {
      icon.textContent = icons.chevronRight
      const actions = document.createElement('div'),
        childrenContainer = document.createElement('div')
      actions.className = 'item-actions'
      actions.innerHTML = `<button title="新文件" data-action="new-file">${icons.newFile}</button><button title="新文件夹" data-action="new-folder">${icons.newFolder}</button>`
      itemContent.appendChild(actions)
      childrenContainer.className = 'folder-children'
      childrenContainer.style.display = 'none'
      item.appendChild(childrenContainer)
    }
    pEl.appendChild(item)
    return item
  }

  const getParentEl = element => element?.parentElement?.closest('.tree-item')

  const getParentHandle = element => {
    const parentEl = getParentEl(element)
    return parentEl ? handleMap[parentEl.dataset.path] : rootHandle
  }

  fileTreeDiv.addEventListener('click', async e => {
    const item = e.target.closest('.tree-item')
    if (!item) return
    const handle = handleMap[item.dataset.path]
    const action = e.target.dataset.action
    if (action) {
      e.stopPropagation()
      if (!item.classList.contains('open')) await toggleFolder(item)
      promptForNewItem(
        handle,
        item.querySelector('.folder-children'),
        action.includes('file') ? 'file' : 'directory',
        item.dataset.path
      )
    } else {
      if (handle.kind === 'file') await openFile(handle, item)
      else await toggleFolder(item)
    }
  })

  fileTreeDiv.addEventListener('contextmenu', e => {
    e.preventDefault()
    const item = e.target.closest('.tree-item')
    if (!item || !rootHandle) return
    contextTarget = {element: item, handle: handleMap[item.dataset.path]}
    contextMenu.style.left = `${e.clientX}px`
    contextMenu.style.top = `${e.clientY}px`
    contextMenu.style.display = 'block'
  })

  const hideContextMenu = () => (contextMenu.style.display = 'none')
  document.addEventListener('click', hideContextMenu)
  document.addEventListener('keydown', e => {
    if (e.key === 'Escape') hideContextMenu()
  })

  contextMenu.addEventListener('click', e => {
    const action = e.target.dataset.action
    if (action) {
      hideContextMenu()
      if (action === 'delete') handleDelete()
      if (action === 'rename') handleRename()
    }
  })

  const handleDelete = async () => {
    if (!confirm(`确定要删除 "${contextTarget.handle.name}" 吗？此操作不可恢复。`)) return
    try {
      const parentHandle = getParentHandle(contextTarget.element)
      await parentHandle.removeEntry(contextTarget.handle.name, {
        recursive: contextTarget.handle.kind === 'directory',
      })
      const pathToDelete = contextTarget.element.dataset.path
      delete handleMap[pathToDelete]
      if (contextTarget.handle.kind === 'directory') {
        Object.keys(handleMap).forEach(key => {
          if (key.startsWith(pathToDelete + '/')) {
            delete handleMap[key]
          }
        })
      }
      if (activeFile.path && activeFile.path.startsWith(pathToDelete)) {
        activeFile = {}
        editorWrapper.style.display = 'none'
        editorPlaceholder.style.display = 'flex'
        filePathDisplay.textContent = ''
      }
      contextTarget.element.remove()
    } catch (err) {
      alert(`删除失败: ${err.message}`)
    }
  }

  const updateChildPathsAndHandles = (movedEl, oldBasePath, newBasePath) => {
    delete handleMap[oldBasePath]
    movedEl.dataset.path = newBasePath

    if (movedEl.classList.contains('directory')) {
      movedEl.querySelectorAll('.tree-item').forEach(child => {
        const oldChildPath = child.dataset.path
        const newChildPath = oldChildPath.replace(oldBasePath, newBasePath)
        child.dataset.path = newChildPath
        const childHandle = handleMap[oldChildPath]
        if (childHandle) {
          delete handleMap[oldChildPath]
          handleMap[newChildPath] = childHandle
        }
      })
    }
    if (activeFile.path && activeFile.path.startsWith(oldBasePath)) {
      activeFile.path = activeFile.path.replace(oldBasePath, newBasePath)
      filePathDisplay.textContent = activeFile.path
    }
  }

  const handleRename = () => {
    const itemContent = contextTarget.element.querySelector('.tree-item-content'),
      nameSpan = itemContent.querySelector('.tree-item-name'),
      input = document.createElement('input')
    input.className = 'rename-input'
    input.type = 'text'
    input.value = contextTarget.handle.name
    nameSpan.style.display = 'none'
    itemContent.appendChild(input)
    input.focus()
    input.select()
    const finish = async () => {
      const newName = input.value.trim()
      nameSpan.style.display = ''
      input.remove()
      if (!newName || newName === contextTarget.handle.name) return
      try {
        const parentHandle = getParentHandle(contextTarget.element)
        for await (const entry of parentHandle.values()) {
          if (entry.name === newName) {
            alert(`重命名失败: 文件或文件夹 "${newName}" 已存在。`)
            return
          }
        }
      } catch (e) {
        alert(`重命名检查失败: ${e.message}`)
        return
      }
      try {
        await contextTarget.handle.move(newName)
        const oldPath = contextTarget.element.dataset.path
        const newPath = oldPath.substring(0, oldPath.lastIndexOf('/') + 1) + newName
        nameSpan.textContent = newName
        nameSpan.title = newName
        contextTarget.element.dataset.name = newName

        const oldHandle = handleMap[oldPath]
        updateChildPathsAndHandles(contextTarget.element, oldPath, newPath)
        handleMap[newPath] = oldHandle
      } catch (err) {
        alert(`重命名失败: ${err.message}`)
      }
    }
    input.addEventListener('blur', finish)
    input.addEventListener('keydown', e => {
      if (e.key === 'Enter') finish()
      if (e.key === 'Escape') {
        nameSpan.style.display = ''
        input.remove()
      }
    })
  }

  const promptForNewItem = (pHandle, pEl, type, pPath) => {
    if (pEl.querySelector(':scope > .new-item-input')) return
    const input = document.createElement('input')
    input.className = 'new-item-input'
    input.type = 'text'
    input.placeholder = `输入${type === 'file' ? '文件名' : '文件夹名'}...`
    pEl.prepend(input)
    input.focus()
    const create = async () => {
      const name = input.value.trim()
      input.remove()
      if (!name) return
      try {
        const newHandle =
          type === 'file'
            ? await pHandle.getFileHandle(name, {create: true})
            : await pHandle.getDirectoryHandle(name, {create: true})
        createTreeItem(newHandle, pEl, pPath ? `${pPath}/${name}` : name)
      } catch (err) {
        alert(`创建'${name}'失败: 文件或文件夹已存在。`)
      }
    }
    input.addEventListener('blur', create)
    input.addEventListener('keydown', e => {
      if (e.key === 'Enter') create()
      if (e.key === 'Escape') input.remove()
    })
  }

  const toggleFolder = async (item, forceOpen = false, expanded = [], openPath = null) => {
    const isOpen = forceOpen || !item.classList.contains('open')
    item.classList.toggle('open', isOpen)
    item.querySelector('.tree-item-icon').textContent = isOpen
      ? icons.chevronDown
      : icons.chevronRight
    const children = item.querySelector('.folder-children')
    children.style.display = isOpen ? 'block' : 'none'
    if (isOpen && children.innerHTML.trim() === '')
      await renderTree(
        handleMap[item.dataset.path],
        children,
        item.dataset.path,
        expanded,
        openPath
      )
  }

  const openFile = async (handle, el) => {
    if (activeFile.element) activeFile.element.classList.remove('active')
    const path = el ? el.dataset.path : handle.name
    activeFile = {handle, element: el, path: path}
    if (el) el.classList.add('active')
    try {
      const content = await (await handle.getFile()).text()
      editorPlaceholder.style.display = 'none'
      editorWrapper.style.display = 'flex'
      editorEl.value = content
      editorEl.scrollTop = 0
      updateLineNumbers()
      saveFileBtn.disabled = true
      filePathDisplay.textContent = activeFile.path
    } catch (err) {
      filePathDisplay.textContent = `打开文件出错: ${err.message}`
    }
  }

  const saveActiveFile = async () => {
    if (!activeFile.handle || saveFileBtn.disabled) return
    saveFileBtn.disabled = true
    try {
      const writable = await activeFile.handle.createWritable()
      await writable.write(editorEl.value)
      await writable.close()
    } catch (err) {
      saveFileBtn.disabled = false
      alert(`保存文件失败: ${err.message}`)
    }
  }

  fileTreeDiv.addEventListener('dragstart', e => {
    draggedItem = e.target.closest('.tree-item')
    if (draggedItem) {
      setTimeout(() => draggedItem.classList.add('dragging'), 0)
      e.dataTransfer.setData('text/plain', draggedItem.dataset.path)
      e.dataTransfer.effectAllowed = 'move'
    }
  })

  fileTreeDiv.addEventListener('dragend', () => {
    if (draggedItem) draggedItem.classList.remove('dragging')
    draggedItem = null
    document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'))
  })

  fileTreeDiv.addEventListener('dragover', e => {
    e.preventDefault()
    document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'))
    if (!draggedItem) return
    const dropTargetEl = e.target.closest('.tree-item')
    if (dropTargetEl && dropTargetEl.classList.contains('directory')) {
      if (dropTargetEl !== draggedItem && !draggedItem.contains(dropTargetEl)) {
        dropTargetEl.classList.add('drag-over')
      }
    } else if (e.target.closest('#file-tree')) {
      fileTreeDiv.classList.add('drag-over')
    }
  })

  fileTreeDiv.addEventListener('dragleave', e => {
    if (!e.currentTarget.contains(e.relatedTarget)) {
      document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'))
    }
  })

  async function moveDirectoryContents(sourceDirHandle, destDirHandle) {
    for await (const entry of sourceDirHandle.values()) {
      if (entry.kind === 'file') {
        const file = await entry.getFile()
        const newFileHandle = await destDirHandle.getFileHandle(entry.name, {create: true})
        const writable = await newFileHandle.createWritable()
        await writable.write(file)
        await writable.close()
      } else if (entry.kind === 'directory') {
        const newSubDirHandle = await destDirHandle.getDirectoryHandle(entry.name, {create: true})
        await moveDirectoryContents(entry, newSubDirHandle)
      }
    }
  }

  fileTreeDiv.addEventListener('drop', async e => {
    e.preventDefault()
    e.stopPropagation()
    const highlightedItem = document.querySelector('.tree-item.drag-over')
    const isRootDrop = fileTreeDiv.classList.contains('drag-over')
    document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'))
    if (!draggedItem || (!highlightedItem && !isRootDrop)) return

    const draggedItemClone = draggedItem.cloneNode(true)

    const sourcePath = draggedItem.dataset.path
    const sourceHandle = handleMap[sourcePath]
    const sourceParentHandle = getParentHandle(draggedItem)

    let destHandle
    if (highlightedItem) {
      destHandle = handleMap[highlightedItem.dataset.path]
    } else {
      destHandle = rootHandle
    }

    if (!destHandle || destHandle === sourceParentHandle) return

    try {
      for await (const entry of destHandle.values()) {
        if (entry.name === sourceHandle.name) {
          alert(`移动失败: 目标位置已存在同名项目 "${sourceHandle.name}"。`)
          return
        }
      }

      const newHandle =
        sourceHandle.kind === 'file'
          ? await destHandle.getFileHandle(sourceHandle.name, {create: true})
          : await destHandle.getDirectoryHandle(sourceHandle.name, {create: true})

      if (sourceHandle.kind === 'file') {
        const file = await sourceHandle.getFile()
        const writable = await newHandle.createWritable()
        await writable.write(file)
        await writable.close()
      } else {
        await moveDirectoryContents(sourceHandle, newHandle)
      }

      await sourceParentHandle.removeEntry(sourceHandle.name, {
        recursive: sourceHandle.kind === 'directory',
      })

      if (draggedItem.parentElement) {
        draggedItem.remove()
      }

      if (highlightedItem && !highlightedItem.classList.contains('open')) {
        await toggleFolder(highlightedItem, true)
      }

      const newParentEl = highlightedItem
        ? highlightedItem.querySelector('.folder-children')
        : fileTreeDiv
      if (newParentEl) {
        newParentEl.appendChild(draggedItemClone)
      }

      const oldPath = sourcePath
      const newParentPath = highlightedItem ? highlightedItem.dataset.path : ''
      const newPath = newParentPath ? `${newParentPath}/${sourceHandle.name}` : sourceHandle.name

      updateChildPathsAndHandles(draggedItemClone, oldPath, newPath)
      handleMap[newPath] = newHandle
    } catch (err) {
      console.error('移动失败:', err)
      if (err && err.message && err.message.includes('parentElement')) {
        //此处为了处理可能的错误，确保在拖拽时不会因为父元素不存在而导致脚本崩溃,
        //但是实际上由于DOM刷新机制,父元素可能已经不存在,所以这个错误处理可能会一致报错(但实际上是正常的)
      } else {
        alert(`移动文件或文件夹时发生错误: ${err.message}`)
      }
      const expandedPaths = [...document.querySelectorAll('.tree-item.open')].map(
        el => el.dataset.path
      )
      await renderTree(rootHandle, fileTreeDiv, '', expandedPaths, activeFile.path)
    }
  })

  window.addEventListener('dragover', e => e.preventDefault())
  window.addEventListener('drop', async e => {
    e.preventDefault()
    if (e.target.closest('.sidebar')) return
    try {
      const handle = await e.dataTransfer.items[0]?.getAsFileSystemHandle()
      if (!handle || !(await verifyPermission(handle))) return
      if (handle.kind === 'directory') await resetAndInitialize(handle)
      else if (handle.kind === 'file') await initializeUIForSingleFile(handle)
    } catch (err) {}
  })

  saveFileBtn.addEventListener('click', saveActiveFile)
  document.addEventListener('keydown', e => {
    if ((e.ctrlKey || e.metaKey) && e.key === 's') {
      e.preventDefault()
      saveActiveFile()
    }
  })

  initResizer()
  initEditor()
  restoreSession()
})
